﻿/*
 * DATAFLOWCORE
 * 
Copyright 2012 - Mindstorm Multitouch Limited

Author - Bertrand Nouvel

DataFlowCore is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

DataFlowCore is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU Lesser Public License for more details.
*/

using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;

namespace DFlow
{



    public static class Converters
    {
        
        static public Dictionary<string, Dictionary<string, Func<Object, Object, Object>>> converters =
            new Dictionary<string, Dictionary<string, Func<Object, Object, Object>>>();



        static public void RegisterAssembly(System.Reflection.Assembly a, bool overridePreviousConverters=true)
        {
            foreach (System.Type t in a.GetTypes())
            {
                foreach (System.Reflection.MethodInfo mi in t.GetMethods(System.Reflection.BindingFlags.Public | System.Reflection.BindingFlags.Static))
                {
                    foreach (object to in mi.GetCustomAttributes(typeof(DFlow.SpecificConverter), true))
                    {
#if DEBUG_CONVERTERS
                        DFlowCore.Log.Debug("Registering converter " + mi.Name);
#endif
                        System.Type srctype = mi.GetParameters()[0].ParameterType;
                        System.Type dsttype = mi.ReturnType;
                        if (!converters.ContainsKey(srctype.FullName))
                        {
                            converters.Add(srctype.FullName, new Dictionary<string, Func<object, object, object>>());

                        }
                        if (!(converters[srctype.FullName].ContainsKey(dsttype.FullName)))
                        {
                            converters[srctype.FullName].Add(
                                    dsttype.FullName, ((x, y) => (object)mi.Invoke(null, new object[] { x, y }))
                                    );
                        }
                        else
                        {
                            if (!overridePreviousConverters)
                            {
                                // TODO: Add option for this warning (pedantic mode ?)
                                DFlowCore.Log.Warning(System.String.Format("{0} to {1} converter already found not registering new one...", srctype.FullName, dsttype.FullName),0);
                            }
                            else
                            {
                                converters[srctype.FullName][dsttype.FullName] =
                                    ((x, y) => (object)mi.Invoke(null, new object[] { x, y }));

                                // TODO: Add option for this warning (pedantic mode ?)
                                DFlowCore.Log.Warning(System.String.Format("{0} to {1} converter overrided", srctype.FullName, dsttype.FullName), 0);

                            }
                        }
                    }
                    foreach (object to in mi.GetCustomAttributes(typeof(DFlow.GenericConverter), true))
                    {
                        GenericConverter go = (GenericConverter)to;
                        System.Type srctype = mi.GetParameters()[0].ParameterType;
                        System.Type dsttype = mi.ReturnType;
                        Pair<Func<Type, Type, bool>, Func<object, object, Type, object>> p =
                            new Pair<Func<Type, Type, bool>, Func<object, object, Type, object>>(go.compatible,
                                    ((o1, o2, tt) => (mi.Invoke(null, new object[] { o1, o2, tt }))));

                        if (!generic_converters.Contains(p))
                        {
                            generic_converters.Add(p);
                        }
                    }
                }
            }


            CloseTransitively();
        }

        static public void UpdateConverters()
        {
            converters.Clear();
            foreach (System.Reflection.Assembly a in System.AppDomain.CurrentDomain.GetAssemblies())
            {
                Converters.RegisterAssembly(a);
            }
            CloseTransitively();
        }


        static IEnumerable<DFlow.Pair<string, string>> EnumerateDefinedConversions()
        {
            List<string> ls1 = new List<string>(converters.Keys);
            foreach (string s in ls1)
            {
                List<string> ls2 = new List<string>(converters[s].Keys);
                foreach (string t in ls2)
                {
                    yield return new DFlow.Pair<string, string>(s, t);
                }
            }
        }

        static void CloseTransitively()
        {
            // not optimized...
            // so far on specific converters only
            // generic converters would lead to possible crash...
            // i.e. x-y => list<x> -> list<y> , could be iterated indefinetely
            bool cont = true;
            while (cont)
            {
                cont = false;
                foreach (DFlow.Pair<string, string> p1 in EnumerateDefinedConversions()
                        )
                {
                    foreach (DFlow.Pair<string, string> p2 in EnumerateDefinedConversions()
                        )
                    {
                        if ((p1.second == p2.first)
                            && (!(converters[p1.first].ContainsKey(p2.second)))
                            && (p1.first != p2.second)
                            )
                        {
                            cont = true;
                            Func<object, object, object> c1 = converters[p1.first][p1.second];
                            Func<object, object, object> c2 = converters[p2.first][p2.second];
                            converters[p1.first].Add(p2.second,
                                (a, b) =>
                                {
                                    DFlowCore.Log.Warning(
                                        System.String.Format("Indirect conversion has been using a temporary [{0}->{1}->{2}]", p1.first, p2.first, p2.second));
                                    object t1 = c1(a, b);
                                    object t2 = c2(t1, null);
                                    if (!(object.ReferenceEquals(t1, t2)))
                                    {
                                        if (t1 is IDisposable)
                                        {
                                            (t1 as IDisposable).Dispose();
                                        }
                                    }
                                    return t2;
                                });
#if DEBUG_CONVERTERS
                            DFlowCore.Log.Info(
                                    System.String.Format("Adding indirect conversion  [{0}->{1}->{2}]", p1.first, p2.first, p2.second)
                                    );
#endif
                        }
                    }
                }
            }
        }

        public static List<Pair<Func<Type, Type, bool>,
                            Func<Object, Object, Type, Object>>> generic_converters = new List<Pair<Func<Type, Type, bool>, Func<Object, Object, Type, Object>>>
        {
            new Pair<Func<Type, Type, bool>, Func<Object, Object, Type,Object>>() {
                first = (tin, tout) => (tout==typeof(System.String)),
                second = (pin, pout,tt ) => {
                    try {
                        return DFlowCore.BsonUtils.BsonEncode(pin,pin.GetType()).ToString();
                    }
                    catch (System.Exception e) {
                        return pin.ToString()+"/"+(e.ToString());
                    }
                }
            },
            new Pair<Func<Type, Type, bool>, Func<Object, Object, Type,  Object>>() {
                first = (tin, tout) => (tout==typeof(System.Object)),
                second = (pin, pout,tt ) => {                    
                        return pin;                    
                }
            }
        };


        public static Func<Object, Object, Object> GetConverter(System.Type st, System.Type dt, bool specific_only = false)
        {
            Dictionary<string, Func<object, object, object>> rd = null;
            Func<object, object, object> conv = null;
            if (converters.TryGetValue(st.FullName, out rd))
            {
                if (rd.TryGetValue(dt.FullName, out conv))
                {
                    return conv;
                }
            }


            if (!specific_only)
            {
                foreach (Pair<Func<Type, Type, bool>, Func<Object, Object, Type, Object>> cf in generic_converters)
                {
                    if (cf.first(st, dt))
                    {
                        return ((x, y) => cf.second(x, y, dt));
                    }
                }
            }

            return null;
        }
    }


    public class HandleNewConverters
    {
        public HandleNewConverters()
        {
            DFlowCore.Log.Debug("Initializing converters",0);
            Converters.UpdateConverters();
            DFlowCore.Log.Debug("/Initializing converters",0);
            AppDomain.CurrentDomain.AssemblyLoad += (a, b) =>
            {
                DFlowCore.Log.Debug("Handling Converters for domain" + b.LoadedAssembly.FullName,1);
                Converters.RegisterAssembly(b.LoadedAssembly);
            };
        }
        static public HandleNewConverters Instance = new HandleNewConverters();
    }

}



namespace DFlowCore
{
    static public class Converters
    {
        [DFlow.SpecificConverter]
        public static System.Drawing.Bitmap Convert2D_u8_ab_bitmap(DFlowCore.ArrayView<byte> arin, System.Drawing.Bitmap objout) {
                       if (!arin.IsContiguous)
                       {
                           throw new System.ArgumentException("Array have to be contiguous to be transformed into images");
                       }

                       if (arin.ndim == 0)
                       {
                           return null;
                       }


                       if ((arin.ndim==2)||((arin.ndim==3)&&(arin.Shape[2]==1))) {
                           return new System.Drawing.Bitmap(arin.Shape[1],arin.Shape[0],arin.Stride[0],System.Drawing.Imaging.PixelFormat.Format8bppIndexed,arin.dataptr);                          
                       }
                       if ((arin.ndim == 3) && (arin.Shape[2] == 3))
                       {
                           return new System.Drawing.Bitmap(arin.Shape[1], arin.Shape[0], arin.Stride[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, arin.dataptr);
                       }
                       if ((arin.ndim == 3) && (arin.Shape[2] == 4))
                       {
                           return new System.Drawing.Bitmap(arin.Shape[1], arin.Shape[0], arin.Stride[0], System.Drawing.Imaging.PixelFormat.Format32bppArgb, arin.dataptr);
                       }

                       throw new System.Exception("Unsupported dataformat..");
                  }

        [DFlow.SpecificConverter]
        public static System.Drawing.Bitmap Convert2D_u8_bitmap(DFlowCore.ArrayView<short> arin, System.Drawing.Bitmap objout) {
                       if (!arin.IsContiguous)
                       {
                           throw new System.ArgumentException("Array have to be contiguous to be transformed into images");
                       }

                       if (arin.ndim == 0)
                       {
                           return null;
                       }


                       if ((arin.ndim==2)||((arin.ndim==3)&&(arin.Shape[2]==1))) {
                           return new System.Drawing.Bitmap(arin.Shape[1],arin.Shape[0],arin.Stride[0],System.Drawing.Imaging.PixelFormat.Format8bppIndexed,arin.dataptr);                          
                       }
                       if ((arin.ndim == 3) && (arin.Shape[2] == 3))
                       {
                           return new System.Drawing.Bitmap(arin.Shape[1], arin.Shape[0], arin.Stride[0], System.Drawing.Imaging.PixelFormat.Format24bppRgb, arin.dataptr);
                       }
                       if ((arin.ndim == 3) && (arin.Shape[2] == 4))
                       {
                           return new System.Drawing.Bitmap(arin.Shape[1], arin.Shape[0], arin.Stride[0], System.Drawing.Imaging.PixelFormat.Format32bppArgb, arin.dataptr);
                       }

                       throw new System.Exception("Unsupported dataformat..");
                  }
                

        [DFlow.SpecificConverter]
        public unsafe static ArrayView<float> ConvertAVint_AVfloat(ArrayView<int> a, ArrayView<float> b)
        {
            ArrayView<float> res=new ArrayView<float>(a.Shape);
            int te=res.TotalElements;
            for (int i = 0; i < te; i++)
            {
                ((float *)res.dataptr)[i] = (float)(((int *)a.dataptr)[i]);
            }
            return res;
        }

        [DFlow.SpecificConverter]
        public static IEnumerable<float> ConvertAVint_AVfloat(IEnumerable<int> a, IEnumerable<float> b)
        {
            foreach(int v in a)
            {
                yield return ((float)v);
            }
        }


        public static class ConvertConvertibleEnumerationsChecker
        {
            public static bool Validate(Type t1, Type t2)
            {
               return  (t1.IsGenericType&&t2.IsGenericType&&
                   (t1.GetGenericTypeDefinition() == typeof(IEnumerable<int>).GetGenericTypeDefinition())
              &&(t1.GetGenericTypeDefinition() == typeof(IEnumerable<int>).GetGenericTypeDefinition())
              &&(DFlow.Converters.GetConverter(t1.GetGenericArguments()[0],t2.GetGenericArguments()[0])!=null)
              )
              ;
            }
        }

        
        [DFlow.GenericConverter(typeof(ConvertConvertibleEnumerationsChecker))]
        [DFlow.SpecificConverter]
        public static IEnumerator<object> ConvertConvertibleEnumerations(object a,object b, Type t)
        {
            Type t1=a.GetType().GetGenericArguments()[0];
            Type t2=b.GetType().GetGenericArguments()[0];
            Func<object,object,object> converter =DFlow.Converters.GetConverter(t1.GetGenericArguments()[0],t2.GetGenericArguments()[0]);
            object ie1=a.GetType().InvokeMember("GetEnumerator",System.Reflection.BindingFlags.InvokeMethod,null,a,null);
            while ((bool)ie1.GetType().InvokeMember("MoveNext", System.Reflection.BindingFlags.InvokeMethod, null, ie1, null))
            {
                yield return converter(ie1.GetType().GetProperty("Current").GetValue(ie1, null),null);
            }
        }

        public static class ConvertConvertibleListsChecker
        {
            public static bool Validate(Type t1, Type t2)
            {
                return (t1.IsGenericType && t2.IsGenericType &&
                    (t1.GetGenericTypeDefinition() == typeof(List<int>).GetGenericTypeDefinition())
               && (t1.GetGenericTypeDefinition() == typeof(List<int>).GetGenericTypeDefinition())
               && (DFlow.Converters.GetConverter(t1.GetGenericArguments()[0], t2.GetGenericArguments()[0]) != null)
               )
               ;
            }
        }

        //
        [DFlow.GenericConverter(typeof(ConvertConvertibleListsChecker))]
        public static object ConvertConvertibleLists(object a, object b, Type t)
        {
            Type t1 = a.GetType().GetGenericArguments()[0];
            if (b == null)
            {
                b = t.InvokeMember(null,System.Reflection.BindingFlags.CreateInstance,null,null,null);
            }
            Type t2 = b.GetType().GetGenericArguments()[0];
            Type bt2 = b.GetType();
            bt2.InvokeMember("Clear", System.Reflection.BindingFlags.InvokeMethod, null, b, null);
            
            Func<object, object, object> converter = DFlow.Converters.GetConverter(t1,t2);
            object ie1 = a.GetType().InvokeMember("GetEnumerator", System.Reflection.BindingFlags.InvokeMethod, null, a, null);

            System.Reflection.MethodInfo mmovenext = ie1.GetType().GetMethod("MoveNext");
            System.Reflection.MethodInfo madd = bt2.GetMethod("Add");
            System.Reflection.PropertyInfo pcurrent=ie1.GetType().GetProperty("Current");
            while ((bool)mmovenext.Invoke(ie1, System.Reflection.BindingFlags.InvokeMethod, null, null,null))
            {
                //yield return converter(ie1.GetType().GetProperty("Current").GetValue(ie1, null), null);
                madd.Invoke(b,System.Reflection.BindingFlags.InvokeMethod, null, new object[]
                    {
                        converter(pcurrent.GetValue(ie1, null),null)
                    },null);
            }
            return b;
        }


        public static class ConvertConvertibleDictionaryChecker
        {
            public static bool Validate(Type t1, Type t2)
            {
                return (t1.IsGenericType && t2.IsGenericType &&
                    (t1.GetGenericTypeDefinition() == typeof(Dictionary<int,int>).GetGenericTypeDefinition())
               && (t1.GetGenericTypeDefinition() == typeof(Dictionary<int,int>).GetGenericTypeDefinition())
               && (t1.GetGenericArguments()[0]== t2.GetGenericArguments()[0])
               && (DFlow.Converters.GetConverter(t1.GetGenericArguments()[1], t2.GetGenericArguments()[1]) != null)
               )
               ;
            }
        }

        [DFlow.GenericConverter(typeof(ConvertConvertibleDictionaryChecker))]
        public static object ConvertConvertibleDictionaries(object a, object b, Type t)
        {
            Type t1 = a.GetType().GetGenericArguments()[1];
            if (b == null)
            {
                b = t.InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null);
            }

            Type t2 = b.GetType().GetGenericArguments()[1];
            Type bt2 = b.GetType();
            bt2.InvokeMember("Clear", System.Reflection.BindingFlags.InvokeMethod, null, b, null);

            Func<object, object, object> converter = DFlow.Converters.GetConverter(t1, t2);
            object ie1 = a.GetType().InvokeMember("GetEnumerator", System.Reflection.BindingFlags.InvokeMethod, null, a, null);

            System.Reflection.MethodInfo mmovenext = ie1.GetType().GetMethod("MoveNext");
            System.Reflection.MethodInfo madd = bt2.GetMethod("Add");
            System.Reflection.PropertyInfo pcurrent = ie1.GetType().GetProperty("Current");
            System.Reflection.PropertyInfo pkey = pcurrent.PropertyType.GetProperty("Key");
            System.Reflection.PropertyInfo pvalue = pcurrent.PropertyType.GetProperty("Value");
            while ((bool)mmovenext.Invoke(ie1, System.Reflection.BindingFlags.InvokeMethod, null, null, null))
            {
                //yield return converter(ie1.GetType().GetProperty("Current").GetValue(ie1, null), null);
                madd.Invoke(b, System.Reflection.BindingFlags.InvokeMethod, null, new object[]
                    {
                        pkey.GetValue(pcurrent.GetValue(ie1, null),null),
                        converter(pvalue.GetValue(pcurrent.GetValue(ie1, null),null),null)
                    }, null);
            }
            return b;
        }


        public static class ConvertConvertibleDictionaryToListChecker
        {
            public static bool Validate(Type t1, Type t2)
            {
                return (t1.IsGenericType && t2.IsGenericType &&
                    (t1.GetGenericTypeDefinition() == typeof(Dictionary<int, int>).GetGenericTypeDefinition())
               && (t1.GetGenericTypeDefinition() == typeof(List<int>).GetGenericTypeDefinition())
               && (DFlow.Converters.GetConverter(t1.GetGenericArguments()[1], t2.GetGenericArguments()[0]) != null)
               )
               ;
            }
        }

        [DFlow.GenericConverter(typeof(ConvertConvertibleDictionaryToListChecker))]
        [DFlow.SpecificConverter]
        public static object ConvertConvertibleDictionaryToList(object a, object b, Type t)
        {
            Type t1 = a.GetType().GetGenericArguments()[1];
            if (b == null)
            {
                b = t.InvokeMember(null, System.Reflection.BindingFlags.CreateInstance, null, null, null);
            }

            Type t2 = b.GetType().GetGenericArguments()[1];
            Type bt2 = b.GetType();
            bt2.InvokeMember("Clear", System.Reflection.BindingFlags.InvokeMethod, null, b, null);

            Func<object, object, object> converter = DFlow.Converters.GetConverter(t1, t2);
            object ie1 = a.GetType().InvokeMember("GetEnumerator", System.Reflection.BindingFlags.InvokeMethod, null, a, null);

            System.Reflection.MethodInfo mmovenext = ie1.GetType().GetMethod("MoveNext");
            System.Reflection.MethodInfo madd = bt2.GetMethod("Add");
            System.Reflection.PropertyInfo pcurrent = ie1.GetType().GetProperty("Current");
            System.Reflection.PropertyInfo pvalue = pcurrent.PropertyType.GetProperty("Value");
            while ((bool)mmovenext.Invoke(ie1, System.Reflection.BindingFlags.InvokeMethod, null, null, null))
            {
                //yield return converter(ie1.GetType().GetProperty("Current").GetValue(ie1, null), null);
                madd.Invoke(b, System.Reflection.BindingFlags.InvokeMethod, null, new object[]
                    {
                        converter(pvalue.GetValue(pcurrent.GetValue(ie1, null),null),null)
                    }, null);
            }
            return b;
        }
        



        [DFlow.SpecificConverter]
        public static IEnumerable<int> ConvertAVint_Enum(ArrayView<int> a,IEnumerable<int> b)
        {
            return a;
        }

        [DFlow.SpecificConverter]
        public static IEnumerable<float> ConvertAVFloat_Enum(ArrayView<float> a, IEnumerable<float> b)
        {
            return a;
        }

        [DFlow.SpecificConverter]
        public static Mathf.Vector2 ConvertPointF_UnityVec2(System.Drawing.PointF a, Mathf.Vector2 b)
        {
            return new Mathf.Vector2(a.X, a.Y);
        }

        [DFlow.SpecificConverter]
        public static System.Drawing.PointF ConvertPoint_PointF(System.Drawing.Point a, System.Drawing.Point b)
        {
            return new System.Drawing.PointF(a.X,a.Y);            
        }


        [DFlow.SpecificConverter]
        public static Mathf.Vector2 ConvertPoint_UnityVec2(System.Drawing.Point a, Mathf.Vector2 b)
        {
            return new Mathf.Vector2(a.X, a.Y);
        }

        

        [DFlow.SpecificConverter]
        public static IEnumerable<float> ConvertEnumint_AVfloat(IEnumerable<int> a, IEnumerable<float> b)
        {
            foreach (int v in a)
            {
                yield return ((float)v);
            }
        }
        

    }
}
